package main

import (
	"fmt"
	"runtime"
	"time"
)

// 协程，比线程更小，十几个goroutine可能在底层就五六个线程
// goroutine比thread更易用、更高效、更轻便
// 1.5以前仅使用单线程，只是并发，要多核处理器并行，需要显示调用runtime.GOMAXPROCS(n)告诉调度器使用多个线程
// goroutine虽然是在进程内，但需要通过通信规范化，不要使用共享数据
func say(s string) {
	for i := 0; i < 5; i++ {
		runtime.Gosched() // 允许其他goroutine运行,cpu时间片给别人，下次某个时候继续恢复执行该goroutine
		fmt.Println(s)
	}
}

func main() {
	go say("world") // 开一个新的goroutines执行
	say("hello")    // 当前goroutines执行

	channels()

	bufferedChannels()
	ranges()
	selects()
	timeouts()
}

func sum(a []int, c chan int) {
	total := 0
	for _, v := range a {
		total += v
	}
	c <- total // send total to c
}

// 默认情况下，channel接收和发送数据都是阻塞的，除非另一端已准备好，这样就使得goroutines同步变的更加简单，而不需要显式lock所谓阻塞
// 如果读取(value := <-ch)将会被阻塞，直接到有数据接收，任何发送(ch<-5)将会被阻塞，直接数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具
func channels() {
	a := []int{7, 2, 8, -9, 4, 0}
	c := make(chan int)
	go sum(a[:len(a)/2], c) // 前半
	go sum(a[len(a)/2:], c) // 后半
	x, y := <-c, <-c        // receive from c

	fmt.Println(x, y, x+y)

}

// go允许指定channel缓冲大小
// 当 value = 0 时，channel 是无缓冲阻塞读写的，当value > 0 时，channel 有缓冲、是非阻塞的，直到写满 value 个元素才阻塞写入。
func bufferedChannels() {
	c := make(chan int, 2) // 修改2为1就报错，修改2为3可以正常运行
	c <- 1
	c <- 2
	fmt.Println(<-c)
	fmt.Println(<-c)
	// 修改为1报：fatal error: all goroutines are asleep - deadlock!
	// 写入与读取都改成1次则正常
}

// Range/Close
func fibonacci(n int, c chan int) {
	x, y := 1, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x+y
	}
	close(c) //
}

//记住应该在生产者的地方关闭channel，而不是消费的地方去关闭它，这样容易引起panic
//另外记住一点的就是channel不像文件之类的，不需要经常去关闭，只有当你确实没有任何发送数据了，或者你想显式的结束range循环之类的
func ranges() {
	c := make(chan int, 10)
	fmt.Println("cap of c:", cap(c))
	go fibonacci(cap(c), c)
	for i := range c { // 能不断读取channel里数据，直到该channel被显示的close
		fmt.Println(i)
	}
}

// 多个channel时，需要select
func fibonacci2(c, quit chan int) {
	x, y := 1, 1
	for {
		select { // select默认是阻塞的，只有当监听的channel中有发送或接收可以进行时才会运行
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
			//default:
			//	fmt.Println("not ready yet?") // 当阻塞时执行
		}
	}
}

// select默认是阻塞的，只有当监听的channel中有发送或接收可以进行时才会运行，当多个channel都准备好的时候，select是随机的选择一个执行的。
// 在select里面还有default语法，select其实就是类似switch的功能，default就是当监听的channel都没有准备好的时候，默认执行的（select不再阻塞等待channel）。
func selects() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 20; i++ {
			fmt.Println(<-c) // 从c读取
		}
		quit <- 0 // 发送到quit
	}()
	fibonacci2(c, quit)
}

// 有时候会出现goroutine阻塞的情况，那么我们如何避免整个程序进入阻塞的情况呢？我们可以利用select来设置超时，通过如下的方式实现：
// Goexit 退出当前执行的goroutine，但是defer函数还会继续调用
// Gosched 让出当前goroutine的执行权限，调度器安排其他等待的任务运行，并在下次某个时候从该位置恢复执行
// NumCPU 返回CPU核数量
// NumGoroutine 返回正在执行的排队的任务总数
// GOMAXPROCS 用来设置可以并行计算的CPU核数最大值，并返回之前的值
func timeouts() {
	c := make(chan int)
	o := make(chan bool)
	fmt.Println("timeout -----")
	go func() {
		for {
			select {
			case v := <-c:
				println(v)
			case <-time.After(5 * time.Second): // 超过5秒
				println("timeout")
				o <- true
				break
			}
		}
	}()
	<-o
}
